#include "gwlib/gw_file.h"

#include <json/json.h>
#include <gwbase/base/logging.h>
#include <gwbase/curl_wrapper.h>
#include <gwbase/http_util.h>

namespace dicom {

static int ParseDeleteFileResult(const std::string& s) {
  Json::Value v;
  Json::Reader r;

  if (r.parse(s, v, false) == false)
    return -1;


  return v["code"].asInt();
}

int DeleteFile(GwToken* t, 
               const std::string& id)
{
  EasyCurl curl;

  CurlHeaders headers;
  headers.AddOAuthHeader(t->token);
  curl.AddHeaders(&headers);

  std::string url = t->server + "/api/file/delete?";
  AppendQueryString(&url, "id", id);
  curl.SetUrl(url);
  curl.SetMethod("DELETE");

  curl.EnableDebugOutput(true);

  std::string response;
  bool ok = curl.PerformText(&response);
  if (!ok || curl.http_status() != 200)
    return -1;

  return ParseDeleteFileResult(response);
}


struct WriteFileCallbackWrapperData {
  WriteFileCallback cb;
  void* data;
};

static int WriteFileCallbackWrapper(const void* buf, size_t b, size_t n, void* data) {
  WriteFileCallbackWrapperData* d = static_cast<WriteFileCallbackWrapperData*>(data);
  int result = d->cb(buf, b * n, d->data);

  LOG_TRACE << "data: " << b*n << ", result: " << result;
  return result;
}

struct ReadFileCallbackWrapperData {
  ReadFileCallback cb;
  void* data;
  size_t size;
  size_t written;
};

static int ReadFileCallbackWrapper(void* buf, size_t b, size_t n, void* data) {
  ReadFileCallbackWrapperData* d = static_cast<ReadFileCallbackWrapperData*>(data);
  if (d->written >= d->size)
    return 0;

  int result = d->cb(buf, b * n, d->data);
  if (result < 0)
    return result;

  if (static_cast<size_t>(result) + d->written > d->size) {
    LOG_ERROR << "length of file content is bigger than size";
    size_t valid = d->size - d->written;
    d->written = d->size;
    return static_cast<int>(valid);
  } 

  if (result == 0 && d->written < d->size) {
    LOG_ERROR << "length of file content is less than size";
    return 0;
  }

  d->written += static_cast<size_t>(result);
  LOG_TRACE << "data: " << b*n << ", result: " << result;
  return result;
}


int DownloadFile(GwToken* t, 
                 const std::string& id,
                 WriteFileCallback cb,
                 void* data)
{
  EasyCurl curl;

  CurlHeaders headers;
  headers.AddOAuthHeader(t->token);
  curl.AddHeaders(&headers);

  std::string url = t->server + "/api/file/download?";
  AppendQueryString(&url, "id", id);
  curl.SetUrl(url);

  WriteFileCallbackWrapperData wd;
  wd.cb = cb;
  wd.data = data;
  curl.SetWriteFunction(WriteFileCallbackWrapper, &wd);

  curl.EnableDebugOutput(true);

  bool ok = curl.Perform();
  if (!ok || curl.http_status() != 200)
    return -1;

  return 0;
}

int UploadFile(GwToken* t, 
               const std::string& pid,
               const std::string& name, 
               size_t size,
               time_t mtime,
               ReadFileCallback cb,
               void* data,
               DirEntry* result)

{
  EasyCurl curl;

  CurlHeaders headers;
  headers.AddOAuthHeader(t->token);
  // headers.AddHeader("Content-Length", size);
  curl.AddHeaders(&headers);

  std::string url = t->server + "/api/file/upload?";
  AppendQueryString(&url, "pid", pid);
  AppendQueryString(&url, "name", name);
  AppendQueryString(&url, "size", size);
  AppendQueryString(&url, "mtime", mtime);
  curl.SetUrl(url);

  ReadFileCallbackWrapperData rd;
  rd.cb = cb;
  rd.data = data;
  rd.size = size;
  rd.written = 0;
  curl.SetPutFileData(ReadFileCallbackWrapper, &rd, size);

  curl.EnableDebugOutput(true);

  std::string response;
  bool ok = curl.PerformText(&response);
  if (!ok || curl.http_status() != 200) {
    return -1;
  }

  return ParseSingleDirEntryResult(response, result);
}

int RenameFile(GwToken* t,
               const std::string& id,
               const std::string& pid,
               const std::string& name)
{
  EasyCurl curl;

  CurlHeaders headers;
  headers.AddOAuthHeader(t->token);
  curl.AddHeaders(&headers);

  std::string url = t->server + "/api/file/rename?";
  AppendQueryString(&url, "id", id);
  AppendQueryString(&url, "pid", pid);
  AppendQueryString(&url, "name", name);
  curl.SetUrl(url);

  curl.EnableDebugOutput(true);
  std::string response;
  bool ok = curl.PerformText(&response);
  if (!ok || curl.http_status() != 200)
    return -1;

  DirEntry de;
  return ParseSingleDirEntryResult(response, &de);
}

}
